/*
 Copyright (c) 2013 MeherTJ G. All rights reserved.
 License: LGPL for personnal study or free software.
 */

#include "zen_image.h"
#include <cmath>

namespace Zen {

	std::shared_ptr<Image> Image::CreateWithData(ePixel format, size_t width, size_t height, void const*data)
	{
		auto image = std::shared_ptr<Image>(new Image());
		image->resetWithData(format, width, height, data);
		return image;
	}
	void Image::resetWithData(ePixel format, size_t width, size_t height, void const*data)
	{
		this->m_width = width;
		this->m_height = height;
		this->m_format = format;
		auto buf = (const char *)(data);
		this->m_buffer.assign(buf, buf + (width*height* (int)format));
	}

	std::shared_ptr<Image> Image::Create(ePixel format, size_t width, size_t height, uint8_t byte)
	{
		auto image = std::shared_ptr<Image>(new Image());
		image->reset(format, width, height, byte);
		return image;
	}

	void Image::reset(ePixel format, size_t width, size_t height, uint8_t byte)
	{
		this->m_width = width;
		this->m_height = height;
		this->m_format = format;
		this->m_buffer.assign(width*height* (int)format, byte);
	}



	void Image::fillByte(uint8_t byte)
	{
		::memset(data(), byte, size());
	}


	void Image::fillAlpha(uint8_t byte)
	{
		if(m_format == ePixel::Grey)
		{
			::memset(data(), byte, size());
		}
		else if(m_format == ePixel::GA)
		{
			auto s = data() + 1;
			auto e = s + size();
			for(; s < e; s += 2) *s = byte;
		}
		else if(m_format == ePixel::RGBA)
		{
			auto s = data() + 3;
			auto e = s + size();
			for(; s < e; s += 4) *s = byte;
		}
	}


	void Image::fillGrey(uint8_t byte)
	{
		if(m_format == ePixel::Grey || m_format == ePixel::RGB)
		{
			::memset(data(), byte, size());
		}
		else if(m_format == ePixel::GA)
		{
			auto s = data();
			auto const e = s + size();
			for(; s < e; s += 2) *s = byte;
		}
		else if(m_format == ePixel::RGBA)
		{
			auto s = data();
			auto const e = s + size();
			for(; s < e; s += 4) s[0] = s[1] = s[2] = byte;
		}
	}


	void Image::fillRGB(Zen::Color3b rgb)
	{
		if(m_format == ePixel::Grey || m_format == ePixel::GA)
		{
			this->fillGrey(rgb.g8);
		}
		else if(m_format == ePixel::RGB || m_format == ePixel::RGBA)
		{
			auto s = data();
			auto const e = s + size();
			for(; s < e; s += (int)m_format)
			{
				s[0] = rgb.r8;
				s[1] = rgb.g8;
				s[2] = rgb.b8;
			}
		}
	}


	void Image::fillRGBA(Zen::Color4b rgba)
	{
		if(m_format == ePixel::Grey)
		{
			fillAlpha(rgba.a8);
		}
		else if(m_format == ePixel::RGB)
		{
			fillRGB(Zen::Color3b(rgba));
		}
		else if(m_format == ePixel::GA)
		{
			auto s = data();
			auto const e = s + size();
			for(; s < e; s += 2)
			{
				s[0] = rgba.g8;
				s[1] = rgba.a8;
			}
		}
		else if(m_format == ePixel::RGBA)
		{
			auto s = data();
			auto const e = s + size();
			for(; s < e; s += 4)
			{
				s[0] = rgba.r8;
				s[1] = rgba.g8;
				s[2] = rgba.b8;
				s[3] = rgba.a8;
			}
		}
	}

	
	bool Image::fill(ePixel format, void const*data)
	{
		if(m_format == ePixel::None || format == ePixel::None)
			return false;

		if(format == m_format)
		{
			::memcpy(this->data(), data, size());
			return true;
		}
		if(m_format == ePixel::RGB || format == ePixel::RGB)
		{
			return fillColor(format, data);
		}

		if(m_format == ePixel::Grey)
		{
			if(format == ePixel::RGBA)
				return fillAlpha(format, data);
			if(format == ePixel::GA)
				return fillColor(format, data);
		}
		if(m_format == ePixel::GA)
		{
			if(format == ePixel::RGBA)
			{
				auto dst = this->data();
				auto src = (uint8_t const*)data;
				auto count = width()*height();
				for(size_t i = 0; i < count; ++i)
				{
					dst[0] = src[1];
					dst[1] = src[3];
					dst += 2;
					src += 4;
				}
				return true;
			}
			if(format == ePixel::Grey)
				return fillColor(format, data);
		}
		if(m_format == ePixel::RGBA)
		{
			if(format == ePixel::GA)
			{
				auto dst = this->data();
				auto src = (uint8_t const*)data;
				auto count = width()*height();
				for(size_t i = 0; i < count; ++i)
				{
					dst[0] = dst[1] = dst[2] = src[0];
					dst[3] = src[1];
					dst += 4;
					src += 2;
				}
				return true;
			}
			if(format == ePixel::Grey)
				return fillAlpha(format, data);
		}
		return false;
	}

	bool Image::fillAlpha(ePixel format, void const*data)
	{
		if(m_format == ePixel::None || format == ePixel::None)
			return false;

		if(format == ePixel::RGB || m_format == ePixel::RGB) return false;

		if(format == m_format && format == ePixel::Grey)
		{
			::memcpy(this->data(), data, size());
			return true;
		}
		auto dst = this->data() + ((int)m_format - 1);
		auto src = (uint8_t const *)data + ((int)format - 1);
		auto count = width()*height();
		for(size_t i = 0; i < count; ++i)
		{
			*dst = *src;
			dst += (int)m_format;
			src += (int)format;
		}
		return true;
	}
	bool Image::fillColor(ePixel format, void const*data)
	{
		if(m_format == ePixel::None || format == ePixel::None)
			return false;

		if(format == m_format)
		{
			if(format == ePixel::RGB || format == ePixel::Grey)
			{
				::memcpy(this->data(), data, size());
				return true;
			}
		}

		int a = (int)m_format;
		int b = (int)format;
		auto dst = this->data();
		auto src = (uint8_t const *)data;
		auto count = width()*height();

		if(a > 2) // rgb, rgba
		{
			if(b > 2) // rgb, rgba
			{
				for(size_t i = 0; i < count; ++i)
				{
					dst[0] = src[0];
					dst[1] = src[1];
					dst[2] = src[2];
					dst += a;
					src += b;
				}
				return true;
			}
			else // g, ga
			{
				for(size_t i = 0; i < count; ++i)
				{
					dst[0] = dst[1] = dst[2] = src[0];
					dst += a;
					src += b;
				}
				return true;
			}
		}
		else
		{
			if(b > 2) // rgb, rgba
			{
				for(size_t i = 0; i < count; ++i)
				{
					dst[0] = src[1];
					dst += a;
					src += b;
				}
				return true;
			}
			else // g, ga
			{
				for(size_t i = 0; i < count; ++i)
				{
					dst[0] = src[0];
					dst += a;
					src += b;
				}
				return true;
			}
		}
		return true;
	}
}
namespace Zen {

	static float s_GetV01(float x, float y, float xv, float yv)
	{
		if(x < 0) xv -= 1;
		if(y < 0) yv -= 1;
		return x*xv + y*yv;
	}

	Color4f ImageEffectRainbow::GetRainbowColor(float v) {
		static Color4f c[8] = {
			Color4b(0xFF0000FF),
			Color4b(0xFF7F00FF),
			Color4b(0xFFFF00FF),
			Color4b(0x00FF00FF),
			Color4b(0x00FFFFFF),
			Color4b(0x0000FFFF),
			Color4b(0xFF00FFFF),
			Color4b(0xFF0000FF),
		};
		v -= floor(v);
		v *= 7;
		int n = (int)v;
		v = v - n;
		return Color4fLerp(c[n], c[n+1], v);

	}
	
	ImageEffectRainbow::ImageEffectRainbow(float angle, float move)
	{
		m_move = move;
		m_x = cosf(angle);
		m_y = sinf(angle);
		auto s = abs(m_x) + abs(m_y);
		m_x /= s;
		m_y /= s;
	}

	std::shared_ptr<Image> ImageEffectRainbow::operator()(std::shared_ptr<Image> image) {
		if(!image || image->format() == ePixel::None) return image;
		if(image->width() == 0 || image->height() == 0) return image;

		std::shared_ptr<Image> image2 = nullptr;
		image2 = Image::Create(ePixel::RGBA, image->width(), image->height(), 255);
		image2->fill(image->format(), image->data());

		image = image2;

		size_t step = 4;
		auto data = image->data();

		std::vector<float> xvs(image->width());
		for(size_t i = 0; i < image->width(); ++i) {
			xvs[i] = (float)i / (float)image->width();
		}
		for(size_t j = 0; j < image->height(); ++j)
		{
			float yv = (float)j / (float)image->height();
			for(size_t i = 0; i < image->width(); ++i)
			{
				float xv = xvs[i];
				auto c =  GetRainbowColor(s_GetV01(m_x, m_y, xv, yv)+m_move);

				if(data[3]) {
					data[0] = (uint8_t)(255*c.red); // data[0]
					data[1] = (uint8_t)(255*c.green); // data[1]
					data[2] = (uint8_t)(255*c.blue); // data[2]
				}
				data += step;
			}
		}
		return image;
	}
}
